//TODO:
//every time tab is pressed, update start not to the current target, but
//to the current position, which is double. make it so every time it takes
//the same time to get to the final position, adjusting the speed accordingly
//and in a way that the last part of the movement happens always at the same
//speed

#include "cshufflerenderer.h"

#include <X11/Xregion.h>

#include <buola/gl.h>

#include <buola/gl/ctexture.h>

#include "scompositecontext.h"
#include "cmanagedclient.h"

namespace buola { namespace bwm {

double SmoothStep(double pX)
{
    return -2*pX*pX*pX+3*pX*pX;
}

double SmoothStep(double pMin,double pMax,double pX)
{
    if(pX<pMin) return 0;
    if(pX>pMax) return 1;
    double lF=((pX-pMin)/(pMax-pMin));
    
    return -2*lF*lF*lF+3*lF*lF;
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                   creation and initialization                 //
//                                                               //
///////////////////////////////////////////////////////////////////

CShuffleRenderer::CShuffleRenderer()
{
}

CShuffleRenderer::~CShuffleRenderer()
{
}

void CShuffleRenderer::Enable()
{
    std::vector<CManagedClient*> lClients;
    mCurrent=-1;
    CManagedClient *lActive=gRoot->GetActiveClient();
    
    mGoingOut=false;

    if(lActive)
    {
        lClients.push_back(lActive);
        mCurrent=0;
    }
    
    for(int i=gRoot->mManagedClients.size()-1;i>=0;i--)
    {
        CManagedClient *lC=gRoot->mManagedClients[i];
        if(lC->ShouldCirculate())
        {
            if(lC!=lActive)
            {
                lClients.push_back(lC);
            }
            mClientData[lC].mCirculable=true;
        }
        else
        {
            mClientData[lC].mCirculable=false;
        }
    }

    int lCount=lClients.size();
    mSlots.resize(lCount);
    mClients.resize(lCount);

    mMaxSize={gRoot->GetSize().x/2.0,gRoot->GetSize().x*0.65};

    for(int i=0;i<lCount;i++)
    {
        CManagedClient *lC=lClients[i];

        mClients[i].mClient=lC;
        
        CSize_d lSize=lC->GetPhysicalSize();
        double lXScale=max(1.0,lSize.x/(mMaxSize.x));
        double lYScale=max(1.0,lSize.y/(mMaxSize.y));
        
        mClients[i].mScale=1.0/max(lXScale,lYScale);
        CPoint_i lPoint=lC->GetPhysicalPos();
        mClients[i].mOrigin={lPoint.x+lSize.x/2,lPoint.y+lSize.y};
    }

    int lBefore=lCount/2;
    int lAfter=lCount-lBefore-1;

//  if(lCount==2) lAfter=1;
    
    for(int i=0;i<lAfter;i++)
    {
        SSlot &lSlot=mSlots[1+i];
        lSlot.mDepth=-i;
        lSlot.mTranslate1={gRoot->GetSize().x*0.85,gRoot->GetSize().y*0.85,-400.0};
        lSlot.mRotate=-45.0;
        lSlot.mTranslate2={0.0,0.0,200.0-120.0*i};
        lSlot.mAdjustment=1.0;
    }
    for(int i=0;i<lBefore;i++)
    {
        SSlot &lSlot=mSlots[lCount-1-i];
        lSlot.mDepth=-i;
        lSlot.mTranslate1={gRoot->GetSize().x*0.15,gRoot->GetSize().y*0.85,-400.0};
        lSlot.mRotate=45.0;
        lSlot.mTranslate2={0.0,0.0,200.0-120.0*i};
        lSlot.mAdjustment=-1.0;
    }
    mSlots[0].mDepth=1;
    mSlots[0].mTranslate1={gRoot->GetSize().x/2,gRoot->GetSize().y*0.85,0.0};
    mSlots[0].mRotate=0.0;
    mSlots[0].mTranslate2={0.0,0.0,0.0};
    mSlots[0].mAdjustment=0.0;

    mInStep=0.0;

    for(int i=0;i<mClients.size();i++)
    {
        mClients[i].mSlotStart=i;
        mClients[i].mSlotCurrent=i;

        mClients[i].mDepthPos=mSlots[i].mDepth;
        mClients[i].mTranslate1Pos=mSlots[i].mTranslate1;
        mClients[i].mRotatePos=mSlots[i].mRotate;
        mClients[i].mTranslate2Pos=mSlots[i].mTranslate2;
    }
    
    mShuffleStep=0.0;
    mFirst=true;

    gRoot->DamageAll();
}

void CShuffleRenderer::Disable()
{
    gRoot->DamageAll();
}

void CShuffleRenderer::Forward()
{
    mCurrent=(mCurrent+1)%mClients.size();
    gRoot->DamageAll();

    if(mFirst)
    {
        for(int i=0;i<mClients.size();i++)
        {
            mClients[i].mSlotCurrent--;
            mClients[i].mSlotStart=mClients[i].mSlotCurrent;
        }

        mFirst=false;
    }
    else
    {
        for(int i=0;i<mClients.size();i++)
        {
            mClients[i].mSlotStart=mClients[i].mSlotCurrent;
            mClients[i].mSlotCurrent--;
        }
    }
    
    mShuffleStep=0.0;
}

void CShuffleRenderer::Backward()
{
    mCurrent=(mCurrent+mClients.size()-1)%mClients.size();
    gRoot->DamageAll();

    if(mFirst)
    {
        for(int i=0;i<mClients.size();i++)
        {
            mClients[i].mSlotCurrent++;
            mClients[i].mSlotStart=mClients[i].mSlotCurrent;
        }

        mFirst=false;
    }
    else
    {
        for(int i=0;i<mClients.size();i++)
        {
            mClients[i].mSlotStart=mClients[i].mSlotCurrent;
            mClients[i].mSlotCurrent++;
        }
    }
    
    mShuffleStep=0.0;
}

void CShuffleRenderer::GoIn()
{
    mGoingOut=false;
}

void CShuffleRenderer::GoOut()
{
    mGoingOut=true;
}

CManagedClient *CShuffleRenderer::GetCurrentClient()
{
    return mClients[mCurrent].mClient;
}

void CShuffleRenderer::Render(int pDesktop,ERenderMode pMode)
{
    glColor3f(1.0,0.3,0.7);
    glNormal3f(0,0,-1);

    SGLCompositeContext lContext;
    lContext.mDesktop=pDesktop;
    if(gRoot->GetFlag(COMP_ALLDAMAGE))
    {
        lContext.mRemaining=gRoot->mRegions.mDesktop;
    }
    else
    {
        if(gRoot->GetFlag(COMP_DESKTOPDAMAGE))
        {
            lContext.mRemaining|=gRoot->mRegions.mDamage[pDesktop]&gRoot->mRegions.mDesktop;
        }
        if(gRoot->GetFlag(COMP_VIEWPORTDAMAGE))
        {
            lContext.mRemaining|=gRoot->mRegions.mViewportDamage;
        }
        if(lContext.mRemaining.size()>3)
            lContext.mRemaining=lContext.mRemaining.BoundingBox();
        gRoot->mRegions.mPainted|=lContext.mRemaining;
    }
    gRoot->mRegions.mDamage[pDesktop].clear();

    lContext.mPrevTrans=nullptr;

    for(int i=gRoot->mClientsStack.size()-1;i>=0;i--)
    {
        CClient *lC=gRoot->mClientsStack[i];
        if(mClientData[lC].mCirculable) continue;
        lC->Clip(lContext);
    }
    
    for(CClient *lC=lContext.mPrevTrans;lC;lC=lC->mPrevTrans)
    {
        if(mClientData[lC].mCirculable) continue;
        lC->Paint(lContext);
    }

//    double lInStep=SmoothStep(mInStep);
    
    std::vector<SClient*> lSorted;
    
    for(int i=0;i<mClients.size();i++)
        lSorted.push_back(&mClients[i]);
        
    std::sort(lSorted.begin(),lSorted.end(),ADepthSort());

    for(int i=0;i<lSorted.size();i++)
    {
        throw XNotImplemented("shuffle renderer");
//         glPushMatrix();
//         gl::translate(lSorted[i]->mOrigin+(lSorted[i]->mTranslate1Pos-lSorted[i]->mOrigin)*lInStep);
//         glRotated(lSorted[i]->mRotatePos*lInStep,0.0,1.0,0.0);
//         gl::translate(lSorted[i]->mTranslate2Pos*lInStep);
//         glScaled(1.0+(lSorted[i]->mScale-1.0)*lInStep,
//                  1.0+(lSorted[i]->mScale-1.0)*lInStep,1.0);
//         glNormal3f(0,0,-1);
//         lSorted[i]->mClient->PaintBaseLine(pDesktop);
//         glPopMatrix();
    }
}

void CShuffleRenderer::OnLoop()
{
    if(mGoingOut)
    {
        mInStep-=(to_double_seconds(gRoot->GetTime().mElapsed))/0.5;
        gRoot->DamageAll();
        if(mInStep<0.0)
        {
            mInStep=0.0;
            sOutFinished();
            mGoingOut=false;
        }
    }
    else if(mInStep<1.0)
    {
        mInStep+=(to_double_seconds(gRoot->GetTime().mElapsed))/0.5;
        gRoot->DamageAll();
        if(mInStep>1.0)
            mInStep=1.0;
    }
    if(mShuffleStep<1.0)
    {
        mShuffleStep+=(to_double_seconds(gRoot->GetTime().mElapsed))/0.3;
        gRoot->DamageAll();
        if(mShuffleStep>1.0)
            mShuffleStep=1.0;

        double lShuffleStep=SmoothStep(mShuffleStep);

        for(int i=0;i<mClients.size();i++)
        {
            SClient &lClient=mClients[i];
        
            double lSlotPos=lClient.mSlotStart+
                    (lClient.mSlotCurrent-lClient.mSlotStart)*lShuffleStep;

            int lBefore=(int)floor(lSlotPos);
            double lStep=lSlotPos-lBefore;

            int lIntPos=lBefore%((int)mClients.size());
            if(lIntPos<0) lIntPos+=mClients.size();
            const SSlot &lSlot1=mSlots[lIntPos];
            const SSlot &lSlot2=mSlots[(lIntPos+1)%mClients.size()];

            lClient.mDepthPos=lSlot1.mDepth+
                            (lSlot2.mDepth-lSlot1.mDepth)*lStep;
            lClient.mTranslate1Pos=lSlot1.mTranslate1+
                (lSlot2.mTranslate1-lSlot1.mTranslate1)*lStep;
            lClient.mRotatePos=lSlot1.mRotate+
                (lSlot2.mRotate-lSlot1.mRotate)*lStep;
            lClient.mTranslate2Pos=lSlot1.mTranslate2+
                (lSlot2.mTranslate2-lSlot1.mTranslate2)*lStep;
            double lXExtra=mMaxSize.x-
                    lClient.mClient->GetPhysicalSize().x*lClient.mScale;
                            
            lClient.mTranslate2Pos.x()+=lXExtra/2*(lSlot1.mAdjustment+
                    (lSlot2.mAdjustment-lSlot1.mAdjustment)*lStep);
        }
    }
}

/*namespace bwm*/ } /*namespace buola*/ }

